﻿#region License | GNU GPL v3

//Up2Date Framework - Update framework for application developers
//Copyright (C) 2012  Aldo Giese IT-Services

//This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License 
//as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
//of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

//You should have received a copy of the GNU General Public License along with this program;
//if not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using HashLib;
using SevenZip;

namespace Up2DateFramework
{
    public class U2D
    {
        #region Fields ---------------------------------------------------------------------------------------------------

        /// <summary>
        /// Holds the files that have been updated
        /// </summary>
        private static readonly HashSet<string> LogBuffer = new HashSet<string>();

        /// <summary>
        /// Hält die Lognachrichten
        /// </summary>
        private static string _logBuffer;

/*
        /// <summary>
        /// Holds the modified file indexes
        /// </summary>
        private static List<int> _indexerList = new List<int>();
*/
        /// <summary>
        /// Hashing Interface
        /// </summary>
        private static IHash _hash;

        #endregion -------------------------------------------------------------------------------------------------------

        #region Properties------------------------------------------------------------------------------------------------

        /// <summary>
        /// Remote update Datei
        /// </summary>
        public static string RemoteUpdateFile { get; set; }

        /// <summary>
        /// Der Remote Update Pfad (Beginnt mit http://)
        /// </summary>
        public static string RemoteUpdatePath { get; set; }

        /// <summary>
        /// Der Lokale Update Pfad
        /// </summary>
        public static string LocalUpdatePath { get; set; }

        /// <summary>
        /// Log Aktivierung
        /// </summary>
        public static bool ActivateLogOutput { get; set; }

        /// <summary>
        /// Splitter Zeichen entfernen bei langen Hashes ("-")
        /// </summary>
        public static bool RemoveSplitChar { get; set; }

        /// <summary>
        /// Standard Separator char
        /// </summary>
        public static string StandardSeparator { get; set; } 

        /// <summary>
        /// Update log file name
        /// </summary>
        public static string UpdateLogName { get; set; }

        /// <summary>
        /// Update file name (default: versions.txt)
        /// </summary>
        public static string UpdateFileName { get; set; }

        /// <summary>
        /// Hält alle möglichen Checksummen Typen
        /// Dient sozusagen als Switch zwischen der externen Anwendung zur HashLib ohne diese explizit
        /// in das eigene Projekt als Referenz einzufügen. Damit bleibt das Backend aufgeräumter.
        /// </summary>
        public enum ChecksumsHashes
        {
            Adler32,
            Crc32,
            Crc32A,
            Crc64,
            Crc64A,
            Crc64B,
            Crc32B,
            AP,
            Bernstein,
            Bernstein1,
            BKDR,
            DEK,
            DJB,
            DotNet,
            ELF,
            FNV,
            FNV1a,
            Jenkins3,
            JS,
            Murmur2,
            OneAtTime,
            PJW,
            Rotating,
            RS,
            SDBM,
            ShiftAndXor,
            SuperFast
        }

        /// <summary>
        /// Der Style der LocalList (entweder als Liste oder als HashSet)
        /// </summary>
        public enum LocalListStyle
        {
            List,
            HashSet
        }

        #endregion -------------------------------------------------------------------------------------------------------

        #region Constructors ---------------------------------------------------------------------------------------------

        /// <summary>
        /// Konstruktor setzen falls der User vergessen sollte einige Setups zu machen
        /// </summary>
        static U2D()
        {
            StandardSeparator = ":";
            ActivateLogOutput = false;
            RemoveSplitChar = false;
        }

        #endregion -------------------------------------------------------------------------------------------------------

        #region Methods --------------------------------------------------------------------------------------------------

        
        /// <summary>
        /// Download the update file from the remote path
        /// </summary>
        /// <param name="adress"></param>
        /// <returns></returns>
        private static string DownloadUpdateFile(string adress)
        {
            WebClient c = new WebClient();
            return Helper.ByteArrayToString(c.DownloadData(adress));
        }

        /// <summary>
        /// Lädt die Dateien die in der 2. Listen aktueller sind
        /// </summary>
        /// <param name="remoteList">Die Remote Liste mit den Daten</param>
        private static void LoadModifiedFiles(HashSet<string> remoteList)
        {
            //for (int i = 0; i < indexerList.Count; i++)
            // DownloadFile(RemoteUpdatePath + UpdateFilename + remoteList.ToList()[indexerList[i]], LocalUpdatePath);
        }

        /// <summary>
        /// Prüft den übergebenen Enum und gibt demenstprechend Meldung weiter an die HashLib
        /// </summary>
        /// <param name="chk"></param>
        internal static void CheckChecksumParams(ChecksumsHashes chk)
        {
            switch (chk)
            {
                case ChecksumsHashes.Adler32:
                    _hash = HashFactory.Checksum.CreateAdler32();
                    break;

                case ChecksumsHashes.Crc32A:
                    _hash = HashFactory.Checksum.CreateCRC32a();
                    break;

                case ChecksumsHashes.Crc32B:
                    _hash = HashFactory.Checksum.CreateCRC32b();
                    break;

                case ChecksumsHashes.Crc64A:
                    _hash = HashFactory.Checksum.CreateCRC64a();
                    break;

                case ChecksumsHashes.Crc64B:
                    _hash = HashFactory.Checksum.CreateCRC64b();
                    break;

                    
                case ChecksumsHashes.AP:
                    _hash = HashFactory.Hash32.CreateAP();
                    break;

                case ChecksumsHashes.Bernstein:
                    _hash = HashFactory.Hash32.CreateBernstein();
                    break;

                case ChecksumsHashes.Bernstein1:
                    _hash = HashFactory.Hash32.CreateBernstein1();
                    break;

                case ChecksumsHashes.DEK:
                    _hash = HashFactory.Hash32.CreateDEK();
                    break;

                case ChecksumsHashes.DJB:
                    _hash = HashFactory.Hash32.CreateDJB();
                    break;

                case ChecksumsHashes.DotNet:
                    _hash = HashFactory.Hash32.CreateDotNet();
                    break;

                case ChecksumsHashes.ELF:
                    _hash = HashFactory.Hash32.CreateELF();
                    break;

                case ChecksumsHashes.FNV:
                    _hash = HashFactory.Hash32.CreateFNV();
                    break;

                case ChecksumsHashes.FNV1a:
                    _hash = HashFactory.Hash32.CreateFNV1a();
                    break;

                case ChecksumsHashes.JS:
                    _hash = HashFactory.Hash32.CreateJS();
                    break;

                case ChecksumsHashes.Jenkins3:
                    _hash = HashFactory.Hash32.CreateJenkins3();
                    break;

                case ChecksumsHashes.Murmur2:
                    _hash = HashFactory.Hash32.CreateMurmur2();
                    break;

                case ChecksumsHashes.OneAtTime:
                    _hash = HashFactory.Hash32.CreateOneAtTime();
                    break;

                case ChecksumsHashes.PJW:
                    _hash = HashFactory.Hash32.CreatePJW();
                    break;

                case ChecksumsHashes.RS:
                    _hash = HashFactory.Hash32.CreateRS();
                    break;

                case ChecksumsHashes.Rotating:
                    _hash = HashFactory.Hash32.CreateRotating();
                    break;

                case ChecksumsHashes.SDBM:
                    _hash = HashFactory.Hash32.CreateSDBM();
                    break;

                case ChecksumsHashes.ShiftAndXor:
                    _hash = HashFactory.Hash32.CreateShiftAndXor();
                    break;

                case ChecksumsHashes.SuperFast:
                    _hash = HashFactory.Hash32.CreateSuperFast();
                    break;
            }
        }

        /// <summary>
        /// Generiert eine Liste (HashSet) der einzelnen Datei-Checksummen
        /// im angegeben Ordner.
        ///  </summary>
        /// <param name="root">Ordner der durchsucht werden soll</param>
        /// <param name="chk">Enum der Checksumme/Hash der benutzt werden soll </param>
        /// <returns>Die überladene HashSet Liste mit der Checksumme, Seperator, Dateiname</returns>
        public static HashSet<string> GenerateLocalList(string root, ChecksumsHashes chk)
        {
            try
            {
                _logBuffer += "\r\n";
                HashSet<string> hs = new HashSet<string>();
                var c = Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories);
                CheckChecksumParams(chk);
                
                foreach (var files in c)
                {
                    string buffer = _hash.ComputeFile(files).ToString();
                    FileInfo fi = new FileInfo(files);

                    if (ActivateLogOutput)
                        _logBuffer += string.Format("#scan: {0} | Filesize: {1} bytes @ {2} \r\ncrc32: {3}\r\n",
                                                    fi.Name, fi.Length,
                                                    DateTime.Now, buffer);
                    hs.Add(RemoveSplitChar
                               ? String.Format("{0}{1}{2}", buffer.Replace("-", string.Empty), StandardSeparator,
                                               fi.Name)
                               : String.Format("{0}{1}{2}", buffer, StandardSeparator, fi.Name));
                }

                if (ActivateLogOutput)
                    _logBuffer += string.Format("\r\n{0} datei wurde angelegt!\r\n", UpdateFileName);
                File.WriteAllLines(Path.Combine(Environment.CurrentDirectory, UpdateFileName), hs);
                return hs;
            }

            catch (UnauthorizedAccessException)
            {
                // "Nicht genügend Rechte um Datei / Ordner zu öffnen" Meldung überspringen
            }

            return null;
        }

        /// <summary>
        /// Generiert ein Hashset oder eine Liste aller Dateien im angegebenen Ordner
        /// </summary>
        /// <param name="root">Der Ordner der durchsucht werden soll</param>
        /// <param name="listStyle">Der ListenStyle</param>
        /// <returns>Gibt ein HashSet zurück</returns>
        public static object GenerateLocalList(string root, LocalListStyle listStyle)
        {
            try
            {
                if (listStyle == LocalListStyle.HashSet)
                {
                    HashSet<string> hs = new HashSet<string>();
                    var c = Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories);

                    foreach (FileInfo fi in c.Select(files => new FileInfo(files)))
                        hs.Add(String.Format(fi.FullName));

                    return hs;
                }

                if(listStyle == LocalListStyle.List)
                {
                    var c = Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories);
                    return c.Select(files => new FileInfo(files)).Select(fi => String.Format(fi.FullName)).ToList();
                }
            }

            catch (UnauthorizedAccessException)
            {
                // "Nicht genügend Rechte um Datei / Ordner zu öffnen" Meldung überspringen
            }

            return null;
        }
        

  
        /// <summary>
        /// Methode die die Log Datei schreibt
        /// </summary>
        /// <param name="outputPath">Der Pfad der Datei (ohne Dateinamen)</param>
        /// <param name="logData">Die Daten die geloggt werde sollen</param>
        /// <returns>Ausgabe Nachricht ob erfolg oder nicht</returns>
        public static string LogToFile(string outputPath, HashSet<string> logData )
        {
            if (outputPath == null) throw new ArgumentNullException("outputPath");
            if (logData == null) throw new ArgumentNullException("logData");
            if (ActivateLogOutput)
                try
                {
                    //TODO: Using filestream to minimize file handles
                    File.AppendAllText(Path.Combine(outputPath, UpdateLogName),
                                       String.Format(
                                           "\r\n=========================== Neue Log Datei angelegt @ {0}\r\n",
                                           DateTime.Now));
                    File.AppendAllLines(Path.Combine(outputPath,UpdateLogName),logData);
                    File.AppendAllLines(Path.Combine(outputPath, UpdateLogName), LogBuffer);

                    
                    File.AppendAllLines(Path.Combine(outputPath,UpdateLogName),new[] {_logBuffer});

                    return "Schreibe Log Datei nach : " + Path.Combine(outputPath, UpdateLogName) + " @ " + DateTime.Now;
                }
                catch
                {
                    return "Fehler beim Log Datei speichern!";
                }
            return "Kein Logging aktiviert!";
        }

        /// <summary>
        /// Generiert die Datei mit den Checksummen und Dateinamen für spätere weiterverarbeitung
        /// </summary>
        /// <param name="outputPath">Pfad zum Speichern der Datei (ohne Dateinamen)</param>
        /// <param name="data">Die Liste mit den Daten</param>
        /// <returns>Gibt einen string zurück der angibt ob die Datei erfolgreich erstellt wurde oder nicht</returns>
        public static string CreateUp2DateFile(string outputPath, HashSet<string> data)
        {
            if (outputPath == null) throw new ArgumentNullException("outputPath");
            if (data == null) throw new ArgumentNullException("data");
            try
            {
                File.WriteAllLines(Path.Combine(outputPath, UpdateFileName), data);
                return true.ToString();
            }
            catch
            {
                return false.ToString();
            }
        }

        /// <summary>
        /// Vergleicht zwei Hashsets und gibt zeigt die unterschiede an
        /// </summary>
        /// <param name="localList">Die Lokale Liste</param>
        /// <param name="remoteList">Die Remote Liste</param>
        /// <returns>Ítems die nicht in der ersten Liste vorhanden sind</returns>
        public static HashSet<string> Test2ListsForNewMembers(HashSet<string> localList, HashSet<string> remoteList)
        {
            HashSet<string> tmp = new HashSet<string>();
            IEnumerable<string> differenceQuery = remoteList.Except(localList);
            foreach (var diff in differenceQuery)
                tmp.Add(diff);

            return tmp;
        }


        public static void Check4Update(string root)
        {

        }

        #endregion -------------------------------------------------------------------------------------------------------
    }
}